#include "../include.h"

#include <cstdio>

#include <thread>

#include "circular_queue.h"
#include "rand.h"
#include "duration.h"

using namespace afcore;

TEST_CASE("Test CCircularQueue", "[algorithm]") {
  CCircularQueue<int> cq(4);

  cq.EnQueue(1);
  printf("size:%u front:%d rear:%d overrun:%u\n", cq.Size(), cq.Front(), cq.Rear(), cq.GetOverrunCount());

  cq.EnQueue(2);
  printf("size:%u front:%d rear:%d overrun:%u\n", cq.Size(), cq.Front(), cq.Rear(), cq.GetOverrunCount());

  cq.EnQueue(3);
  printf("size:%u front:%d rear:%d overrun:%u\n", cq.Size(), cq.Front(), cq.Rear(), cq.GetOverrunCount());

  cq.EnQueue(4);
  printf("size:%u front:%d rear:%d overrun:%u\n", cq.Size(), cq.Front(), cq.Rear(), cq.GetOverrunCount());

  cq.EnQueue(5);
  printf("size:%u front:%d rear:%d overrun:%u\n", cq.Size(), cq.Front(), cq.Rear(), cq.GetOverrunCount());

  for (size_t i = 0; i < cq.Size(); ++i) {
    printf("cq\tindex:%u val:%u\n", i, cq.At(i));
  }

  CCircularQueue<int> cq2 {cq};

//  for (size_t i = 0; i < cq2.Size(); ++i) {
//    printf("cq2\tindex:%u val:%u\n", i, cq2.At(i));
//  }

  while(!cq2.Empty()) {
    printf("cq2\tfornt:%u\n", cq2.Front());
    cq2.DeQueue();
  }
}

#include "mpmc_queue.h"

struct SQueueItem {
  std::thread::id thread_id {0};
  uint32_t val {0};

  SQueueItem() = default;

  SQueueItem(std::thread::id thread_id_in, uint32_t val_in)
    : thread_id(thread_id_in)
    , val(val_in) {
  }

  void Print() {
    printf("id:%u val:%u\n", static_cast<size_t>(std::hash<std::thread::id>()(thread_id)), val);
  }
};

void Produce(std::shared_ptr<CMpmcQueue<SQueueItem>> mpmcq_sptr) {
  for (int i = 0; i < 5; ++i) {
    mpmcq_sptr->Enqueue(SQueueItem{std::this_thread::get_id(), CRandom::Instance().Rand32()});
    std::this_thread::sleep_for(RMilliseconds(10));
  }
}

void ProduceNoWait(std::shared_ptr<CMpmcQueue<SQueueItem>> mpmcq_sptr) {
  for (int i = 0; i < 5; ++i) {
    mpmcq_sptr->EnqueueNoWait(SQueueItem{std::this_thread::get_id(), CRandom::Instance().Rand32()});
    std::this_thread::sleep_for(RMilliseconds(10));
  }
}

bool ProcessNext(std::shared_ptr<CMpmcQueue<SQueueItem>> mpmcq_sptr) {
  std::this_thread::sleep_for(RMilliseconds(10));
  SQueueItem item;
  bool dequeued = mpmcq_sptr->Dequeue(item, RMilliseconds(20));
  if (!dequeued) {
    return true;
  }
  item.Print();
  printf("overrun:%u\n", mpmcq_sptr->GetOverrunCount());
  return true;
}

void Consumption(std::shared_ptr<CMpmcQueue<SQueueItem>> mpmcq_sptr) {
  int count = 0;
  while(ProcessNext(mpmcq_sptr)) {
    ++count;
    if (count > 20) {
      break;
    }
  }
}

TEST_CASE("Test CMpmcQueue", "[algorithm]") {
  std::shared_ptr<CMpmcQueue<SQueueItem>> mpmcq_sptr = std::make_shared<CMpmcQueue<SQueueItem>>(3);

  std::vector<std::thread> threads;

  for (int i = 0; i < 3; ++i) {
    threads.emplace_back(std::thread(Produce, mpmcq_sptr));
  }

  for (int i = 0; i < 2; ++i) {
    threads.emplace_back(std::thread(ProduceNoWait, mpmcq_sptr));
  }

  threads.emplace_back(std::thread(Consumption, mpmcq_sptr));

  for (auto&& t : threads) {
    t.join();
  }
}